classdef SequentialOptimizer < handle
% Sequential optimization using dynamic programming

	properties
	scoresSoFar;
	correspondingIndices;
	lastChoices;
	transCost;
	currentSeqId;
	end

	methods
	function obj = SequentialOptimizer(seqLength, transCost)
	% Input:
	%   transCost: a function handle to calculate transition cost 
	%     (a large value means cheaper cost);
		obj.scoresSoFar = cell(seqLength, 1);
		obj.correspondingIndices = cell(seqLength, 1);
		obj.lastChoices = cell(seqLength, 1);

		obj.transCost = transCost;
		obj.currentSeqId = 0;
	end


	function choices = newFrame(obj, weights, indices)
	% Input:
	%   weights: 1-by-k vectors;
	%   indices: corresponding indices of `weights`;
		alpha = 1;  % weighting parameter of temporal cue

		obj.currentSeqId = obj.currentSeqId + 1;
		cid = obj.currentSeqId;

		k = size(weights, 2);
		obj.correspondingIndices{cid} = indices;

		if cid == 1
			obj.scoresSoFar{cid} = weights;
			obj.lastChoices{cid} = zeros(1, k);
			return;
		end

		indLast = obj.correspondingIndices{cid - 1};

		scores = zeros(1, k);
		choices = zeros(1, k);

		for i = 1:k
			% `obj.transCost` returns k-by-1 array
			candidates = obj.scoresSoFar{cid - 1} + ...
				alpha * obj.transCost(indLast, indices(i))' + weights(i);

			[scores(i), choices(i)] = max(candidates);
		end

		obj.scoresSoFar{cid} = scores;
		obj.lastChoices{cid} = choices;
	end

	function choices = makeChoices(obj)
		cid = obj.currentSeqId;
		if cid ~= size(obj.scoresSoFar, 1)
			warning('SequentialOptimizer:makeChoice', ...
				'Current sequence id not equal to the expected length of sequence');
		end

		choices = zeros(cid, 1);
		[~, tempChoices] = max(obj.scoresSoFar{cid});

		for i = cid:-1:1
			choices(i) = obj.correspondingIndices{i}(tempChoices);
			tempChoices = obj.lastChoices{i}(tempChoices);		
		end
	end
	end

end